package main

import (
	"errors"
	"fmt"
	alb20200616 "github.com/alibabacloud-go/alb-20200616/v2/client"
	openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
	"github.com/alibabacloud-go/tea/tea"
	"log"
	"net/http"
	"os/exec"
	"strconv"
	"strings"
	"time"
)

func main() {
	// 读取配置文件
	LoadConfig()

	client, _err := CreateClient()
	if _err != nil {
		log.Printf("Error creating client: %v", _err)
		return
	}

	service, _err := queryService(AppConfig.App.Ip, client)
	log.Printf("service:%+v", service)
	if _err != nil {
		log.Printf("Failed to query service: %v", _err)
		return
	}

	_err = updateWeight(service, tea.Int32(0), client)
	if _err != nil {
		log.Printf("Failed to update weight: %v\n", _err)
		return
	}

	// 查询是否更新权重完成
	for i := 0; i < AppConfig.WeightCheck.CheckNum; i++ {
		log.Printf("开始第%d次权重检查", i+1)
		targetService, queryError := queryService(AppConfig.App.Ip, client)
		if queryError != nil {
			log.Printf("Failed check query service: %v", queryError)
			return
		}
		if targetService == nil {
			log.Printf("targetService is null")
			return
		}
		log.Printf("check targetService:%+v", targetService)
		if *targetService.Status == "Available" {
			break
		}
		time.Sleep(AppConfig.WeightCheck.CheckTimeout * time.Second)
	}

	// 检查进程是否运行中
	pid, _err := getPid(AppConfig.App.AppName)
	if _err != nil {
		// 进程不存在，开始备份操作
		appBak()
	} else {
		// 循环检查进程是否关闭
		for i := 0; i <= 15; i++ {
			log.Printf("开始第%d次进程关闭检查", i+1)
			if i%3 == 0 {
				stopErr := stopPid(pid)
				if stopErr != nil {
					log.Printf("Failed to stop pid: %v", stopErr)
				}
			}

			pid, _err = getPid(AppConfig.App.AppName)
			if _err != nil || len(pid) <= 0 {
				break
			}

			time.Sleep(AppConfig.HealthCheck.CheckProcessTimeout * time.Second)
		}

		appBak()
	}

	// 解压文件
	unZip()

	_err = startPid(AppConfig.App.AppName)
	if _err != nil {
		log.Printf("Failed to startPid: %v", _err)
		return
	}

	_, _err = getPid(AppConfig.App.AppName)
	if _err != nil {
		log.Printf("Failed to getPid: %v", _err)
		return
	}

	// 进行健康检查，每次间隔10s
	for i := 0; i < AppConfig.HealthCheck.CheckNum; i++ {
		log.Printf("开始第%d次健康检查", i+1)
		healthFlag, checkError := healthCheck()
		if healthFlag {
			// 恢复权重
			updateError := updateWeight(service, tea.Int32(100), client)
			if updateError != nil {
				log.Printf("Failed to update weight: %v", updateError)
				return
			}
			break
		}
		log.Printf("health check error: %s", checkError.Error())
		time.Sleep(AppConfig.HealthCheck.CheckTimeout * time.Second)
	}

	log.Printf("进程启动完成")
}

func CreateClient() (_result *alb20200616.Client, _err error) {
	// 工程代码泄露可能会导致 AccessKey 泄露，并威胁账号下所有资源的安全性。以下代码示例仅供参考。
	// 建议使用更安全的 STS 方式，更多鉴权访问方式请参见：https://help.aliyun.com/document_detail/378661.html。
	config := &openapi.Config{
		// 必填，请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_ID。
		AccessKeyId: tea.String(AppConfig.Alb.AccessKeyId),
		// 必填，请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_SECRET。
		AccessKeySecret: tea.String(AppConfig.Alb.AccessKeySecret),
	}
	// Endpoint 请参考 https://api.aliyun.com/product/Alb
	config.Endpoint = tea.String(AppConfig.Alb.Endpoint)
	_result = &alb20200616.Client{}
	_result, _err = alb20200616.NewClient(config)
	return _result, _err
}

func getPid(pidName string) (string, error) {
	cmd := exec.Command("bash", "-c", "ps -ef | grep "+pidName+" | grep -v grep")
	out, err := cmd.Output()
	if err != nil {
		log.Printf("getPid Error: %v", err)
		return "", err
	}
	log.Println("getPid result:", string(out))
	lines := strings.Split(string(out), "\n")
	for _, line := range lines {
		log.Println("getPid line:", line)
		if !strings.Contains(line, "alb-tool") && !strings.Contains(line, "grep") {
			// 提取 PID（第二列）
			parts := strings.Fields(line)
			if len(parts) > 1 {
				return parts[1], nil
			}
		}
	}
	return "", errors.New("pid not found")
}

func stopPid(pid string) error {
	cmd := exec.Command("bash", "-c", "kill -15 "+pid)
	err := cmd.Run()
	if err != nil {
		return fmt.Errorf("failed to kill process with PID %s: %v", pid, err)
	}
	log.Printf("kill process success with PID %s", pid)
	return nil
}

func startPid(pidName string) error {
	appCmd := AppConfig.App.AppCmd
	if strings.Contains(appCmd, "${appName}") {
		appCmd = strings.Replace(appCmd, "${appName}", AppConfig.App.AppName, -1)
	}
	cmd := exec.Command("bash", "-c", appCmd)
	cmd.Dir = AppConfig.App.AppDir
	err := cmd.Run()
	if err != nil {
		return fmt.Errorf("failed to start process with pidName %s: %v", pidName, err)
	}
	log.Printf("start process with pidName %s", pidName)
	return nil
}

func updateWeight(server *alb20200616.ListServerGroupServersResponseBodyServers, weight *int32, client *alb20200616.Client) error {
	item := &alb20200616.UpdateServerGroupServersAttributeRequestServers{
		ServerId:   server.ServerId,
		ServerType: server.ServerType,
		Weight:     weight,
		Port:       server.Port,
	}

	updateRequest := &alb20200616.UpdateServerGroupServersAttributeRequest{
		ServerGroupId: server.ServerGroupId,
		Servers:       []*alb20200616.UpdateServerGroupServersAttributeRequestServers{item},
	}

	result, err := client.UpdateServerGroupServersAttribute(updateRequest)
	if err != nil {
		log.Printf("update weight err:%+v", err)
		return err
	}

	log.Printf("update weight result:%+v", result)
	return nil
}

func queryService(ip string, client *alb20200616.Client) (*alb20200616.ListServerGroupServersResponseBodyServers, error) {
	request := &alb20200616.ListServerGroupServersRequest{}
	request.ServerGroupId = tea.String(AppConfig.Alb.ServerGroupId)

	result, _err := client.ListServerGroupServers(request)
	if _err != nil {
		log.Printf("ListServerGroupServers err:%+v", _err.Error())
		return nil, _err
	}
	servers := result.Body.Servers
	if len(servers) <= 0 {
		return nil, errors.New("no servers found")
	}

	for _, server := range servers {
		if *server.ServerIp == ip {
			return server, nil
		}
	}
	return nil, errors.New("server not found")
}

func healthCheck() (bool, error) {
	resp, err := http.Get(AppConfig.HealthCheck.Url)
	if err != nil {
		return false, err
	}
	if resp.StatusCode == 200 {
		return true, nil
	}
	return false, errors.New("health check failed")
}

func appBak() error {
	if !AppConfig.Back.Enabled {
		return nil
	}

	timestamp := time.Now().Unix()
	backCmd := AppConfig.Back.BackCmd
	if strings.Contains(backCmd, "${timestamp}") {
		backCmd = strings.Replace(backCmd, "${timestamp}", strconv.FormatInt(timestamp, 10), -1)
	}
	cmd := exec.Command("bash", "-c", backCmd)
	cmd.Dir = AppConfig.Back.BackDir
	err := cmd.Run()
	if err != nil {
		log.Printf("appBak error:%+v", err)
		return err
	}
	log.Printf("appBak success")
	return nil
}

func unZip() error {
	if !AppConfig.Unzip.Enabled {
		return nil
	}
	cmd := exec.Command("bash", "-c", AppConfig.Unzip.UnzipCmd)
	cmd.Dir = AppConfig.Unzip.UnzipDir
	err := cmd.Run()
	if err != nil {
		log.Printf("unZip error:%+v", err)
		return err
	}
	log.Printf("unZip success")
	return nil
}
